#include <Process.h>
#include <ProcessManager.h>
#include <SerialDriver.h>
#include <Globals.h>
#include <Asm.h>
#include <Kernel.h>
#include <ElfParser.h>
#include <LinkedList.h>
#include <FileDescriptor.h>
#include <SyscallDispatcher.h>
#include <RpcCall.h>
#include <ResourceManager.h>
#include <Filesystem.h>

Process::Process(PageDirectory *pageDirectory, Address kstack, Address brk,
                 Elf32Parser *_parser, DynamicLinker *_linker, RemoteResource *_resource,
                 MemoryMap *_memoryMap,
                 Process *parent) :
    pageDirectory(pageDirectory),
    kernelStack(kstack),
    breakpoint(brk),
    parent(parent),
    status(Running),
    exitValue(0)
{
  pid         = processManager->nextPid();
  DEBUG_MSG("New process spawned with ID " << dec << pid << ".");

  ring        = 0;
  environment = 0;
  inKernel    = false;
  lastRun     = 0;
  nextFD      = 0;
  errno       = 0;
  
  if (_memoryMap)
    _memoryMap->clone(&memoryMap);
  else
  {
    memoryMap.map(0x00000000, 0x00200000, MemoryMap::Global);
    memoryMap.map(0xC0000000, 0x20000000, MemoryMap::Global);
  }
  
  if (!parent)
  {
    iopl = 3;
    ctty = NULL;
  }
  else
  {
    iopl = parent->iopl;
    ctty = parent->ctty;
  }
  
  if (_parser)
  {
    parser    = _parser->clone();
    linker    = new DynamicLinker(parser);
  }
  else
  {
    parser    = NULL;
    linker    = NULL;
  }

  if (_resource)
  {
    // Clone the remote resource.
    Callable tmp;
    signalResource = new RemoteResource(&tmp);
    Kernel::memcpy((u8int*)signalResource, (u8int*)_resource, sizeof(RemoteResource));
  }
  else
    signalResource = NULL;

  // Initialise the resource and functions.
  resource = new Resource((Callable*)this);
  resource->add((Resource::Call3)&Process::execve2,                   Process_IF::execve);
  resource->add((Resource::Call3)&Process::createFileDescriptor,      Process_IF::createFileDescriptor);
  resource->add((Resource::Call1)&Process::destroyFileDescriptor,     Process_IF::destroyFileDescriptor);
  resource->add((Resource::Call1)&Process::getFileDescriptorResource, Process_IF::getFileDescriptor);
  resource->add((Resource::Call0)&Process::getPid,                    Process_IF::getPid);
  resource->add((Resource::Call1)&Process::sbrk,                      Process_IF::sbrk);
  resource->add((Resource::Call3)&Process::waitPid,                   Process_IF::waitPid);
  resource->add((Resource::Call1)&Process::exit,                      Process_IF::exit);
  resource->add((Resource::Call0)&Process::getMessageSender,          Process_IF::getMessageSender);
  resource->add((Resource::Call0)&Process::getCttyResource,           Process_IF::getCtty);
  resource->add((Resource::Call1)&Process::initialiseSignals,         Process_IF::initialiseSignals);
  resource->add((Resource::Call1)&Process::setCttyName,               Process_IF::setCtty);
}

Process::~Process()
{
  delete resource;
}

u32int Process::fork()
{
  ASSERT(this == processManager->getProcess());

  // Must disable interrupts during.
  stopInterrupts();

  DEBUG_MSG("fork() for process " << dec << getPid() << ". (" << hex << (u32int)this << ")");

  Process *currentProcess = processManager->getProcess();
  
  // This process MUST be the current process. Fork only works this way
  // (stack twiddlage)
  ASSERT(currentProcess == this);
  
  // This implementation of fork() works by messing with return
  // addresses for IRET. So we MUST be in a kernel stack.
  ASSERT(currentProcess->isInKernel());

  // Clone the address space.
  PageDirectory *directory = currentProcess->getPageDirectory()->clone();

//   Address kstack = (Address) new(true) u8int[KERNEL_STACK_SIZE]
//                                        + KERNEL_STACK_SIZE - 4;
  Address kstack = 0x500000-0x4-0x100;

  Process *newProcess = new Process(directory,
                                    kstack,
                                    breakpoint,
                                    parser,
                                    linker,
                                    signalResource,
                                    &memoryMap,
                                    this);

  // Copy all filedescriptors.
  for (int i = 0; i < filedescriptors.length(); i++)
  {
    newProcess->addFileDescriptor(filedescriptors.getByIndex(i)->clone());
  }
  
  // This new process is now our child.
  children.add(newProcess->getPid(), newProcess);
  
  // Put the new process on the ready queue
  processManager->readyQueue.insert(newProcess);
  // Tell the process manager about our new process.
  processManager->processes.add(newProcess->getPid(), newProcess);

  // This will the the entry point for the new process
  Address eip = readInstructionPointer();

  // Here we could be the parent or the child - check.
  if (currentProcess == processManager->getProcess())
  {
    // We are the parent - set up the esp/ebp/eip for our child
    newProcess->setStackPointer(readStackPointer());

    newProcess->setInstructionPointer(eip);
    newProcess->setBasePointer(readBasePointer());

    return newProcess->getPid();
  }
  else
  {
    // We are the child.
    return 0;
  }
}

void Process::executeRpcCalls(RpcCall *call)
{
  do
  {
    // Call the call!
    Callable *target = call->obj;
    switch(call->nparams)
    {
    case 0:
      call->r = (target->*(call->call.call0))();
      break;
    case 1:
      call->r = (target->*(call->call.call1))(call->p1);
      break;
    case 2:
      call->r = (target->*(call->call.call2))(call->p1,call->p2);
      break;
    case 3:
      call->r = (target->*(call->call.call3))(call->p1,call->p2,call->p3);
      break;
    case 4:
      call->r = (target->*(call->call.call4))(call->p1,call->p2,call->p3,call->p4);
      break;
    case 5:
      call->r = (target->*(call->call.call5))(call->p1,call->p2,call->p3,call->p4,call->p5);
      break;
    }
    call->complete = true;

  } while (call = (RpcCall*)syscall(SYSCALL_MORERPC));

}

// TODO:: this function leaks. Previous asynchronous calls are not destroyed.
RpcCall *Process::moreRpc()
{
  RpcCall *c = rpcQueue.removeFirst();
  messageSender = c->sender;
  if (c)
    return c;
  else
  {
    processingRpcs.releaseLock();
    return NULL;
  }
}

void Process::execve2(char *prog, char **argv, char **env)
{

  StringList sl_argv;
  if (argv)
  {
    for (int i = 0; argv[i] != NULL; i++)
    {
      sl_argv.insertLast(argv[i]);
    }
  }  
  StringList sl_env;
  if (env)
  {
    for (int i = 0; env[i] != NULL; i++)
    {
      sl_env.insertLast(env[i]);
    }
  }
  execve(prog, sl_argv, sl_env);
}

void Process::execve(char *prog, StringList argv, StringList env)
{
  char *prog2 = new char[Kernel::strlen(prog)+1];
  Kernel::strcpy(prog2, prog);
  prog = prog2;
  
  parser = new Elf32Parser(prog);
  DEBUG_MSG("execve(\"" << prog << "\"). (" << hex << (u32int)this << ")" );
  ASSERT(this == processManager->getProcess());
  if (!parser->isValid())
  {
    delete parser;
    setErrno(ENOENT);
    return;
  }

  // Put the argv and env lists on the heap.
  int arg_length = argv.getLength();
  int env_length = env.getLength();
  arguments   = new char *[arg_length+1];
  environment = new char *[env_length+1];
  
  int i;
  for (i = 0; i < arg_length; i++)
  {
    // This string is allocated on the heap and will only be destroyed
    // when the program exits.
    char *tmp = argv.removeFirst();
    arguments[i] = new char[Kernel::strlen(tmp)+1];
    Kernel::strcpy(arguments[i], tmp);
    arguments[i][Kernel::strlen(tmp)] = 0;
  }
  arguments[arg_length] = NULL;
  for (i = 0; i < env_length; i++)
  {
    // This string is allocated on the heap and will only be destroyed
    // when the program exits.
    char *tmp = env.removeFirst();
    environment[i] = new char[Kernel::strlen(tmp)+1];
    Kernel::strcpy(arguments[i], tmp);
    environment[i][Kernel::strlen(tmp)] = 0;
  }
  environment[env_length] = NULL;
  
  parser->writeAllSections();
//   ASSERT(Kernel::strcmp(prog, "/mnt/bin/bash"));
  DEBUG_MSG("I R here.");
  // The address breakpoint (the limit of addressable memory below the stack)
  // is the last address loaded in by the ELF loader.
  breakpoint = parser->getLastAddress();
  delete linker;

  linker = new DynamicLinker(parser);
//   ASSERT(Kernel::strcmp(prog, "/mnt/bin/bash"));
  // On returning from this interrupt, the cpu state should be:
  // eax: ptr to argv list. This should be on the kernel heap.
  // ebx: ptr to env list. This should also be on the kernel heap.
  // esp: STACK_START-0x100. To give us some breathing room before we page fault.

  // crt0.s is supposed to do something like:
  // push ebx
  // push eax
  // call _premain
  // hlt <-- should never get here.
  DEBUG_MSG("I R here2.");
  
  DEBUG_MSG("I R here3.");
  Address ip = parser->getEntryPoint();
  u32int *pip = (u32int*)ip;
  
  Registers *regs = getRegistersPtr();
  regs->eax = (u32int)arguments;
  regs->ebx = (u32int)environment;
  regs->eip = ip;
  regs->esp = STACK_START-0x4;
  DEBUG_MSG("I R here4.");
  for (i = 0; i < arg_length; i++)
  {
    DEBUG_MSG("argv["<<dec<<i<<"] = " << arguments[i]);
  }
  
  ASSERT(Kernel::strcmp(prog, "/mnt/bin/bash"));
}

void Process::sendSignal(u32int signal)
{
  if (signalResource)
  {
    DEBUG_VERBOSE("Dispatched signal " << hex << signal << ".");
    signalResource->callSys(0, false, signal);
  }
  else
  {
    DEBUG_VERBOSE("Sending of signal failed.");
  }
}

Address Process::sbrk(s32int incr)
{
  DEBUG_VERBOSE("sbrk(" << hex << incr << ").");
  // TODO: test the saneness of incr.
  if (incr > 0)
  {
    memoryManager.allocateRange(breakpoint,
                                incr);
  }
  breakpoint += incr;
  return breakpoint-incr;
}

void Process::addFileDescriptor(FileDescriptor *fd)
{
  DEBUG_VERBOSE("addFileDescriptor(" << hex << (u32int)fd << ").");
  // Check this index doesn't already exist.
  if (filedescriptors.lookup(fd->getId()))
  {
    ASSERT(false);
    setErrno(EINVAL);
  }
  else
  {
    filedescriptors.add(fd->getId(), fd);
    if (nextFD <= fd->getId())
    {
      nextFD = fd->getId() + 1;
    }
  }
}

u32int Process::createFileDescriptor(char *filename, int mode, int flags)
{
  DEBUG_MSG("createFileDescriptor(\"" << filename << "\", " << hex << mode << ", " << flags << ").");
  FileDescriptor *fd = new FileDescriptor(filename, (FileDescriptor::ModeType)mode,
                                          (FileDescriptor::Flags)flags);
  if (fd->isValid())
  {
    addFileDescriptor(fd);
    return fd->resource->getId();
  }
  else
  {
    delete fd;
    return 0;
  }
}

FileDescriptor *Process::getFileDescriptor(int n)
{
  DEBUG_VERBOSE("getFileDescriptor(" << hex << n << ").");
  FileDescriptor *fd = filedescriptors.lookup(n);
  return fd;
}

u32int Process::getFileDescriptorResource(int n)
{
  FileDescriptor *fd = filedescriptors.lookup(n);
  if (fd)
    return fd->resource->getId();
  else
    return 0;
}
  
FileDescriptor *Process::deleteFileDescriptor(int n)
{
  DEBUG_MSG("deleteFileDescriptor(" << hex << n << ").");
  FileDescriptor *fd = filedescriptors.remove(n);
  return fd;
}

void Process::destroyFileDescriptor(int n)
{
  DEBUG_MSG("destroyFileDescriptor(" << hex << n << ").");
  delete filedescriptors.remove(n);
}

void Process::exit(s32int val)
{
  DEBUG_MSG("Exiting with code " << hex << val << ".");
  // Clear up all filedescriptors.
  for (int i = 0; i < filedescriptors.length(); i++)
  {
    FileDescriptor *fd = filedescriptors.getByIndex(i);
    if (HEAP_ADDRESS(fd))
    {
      memoryManager.free(fd);
    }
    else if (USER_HEAP_ADDRESS(fd))
    {
      memoryManager.ufree(fd);
    }
    else
    {
      kerr << "fd = " << hex << (u32int)fd << ", i = " << (u32int)this <<  endl;
      kerr << "fd = " << hex << (u32int)fd << ", idx=" << filedescriptors.getKeyByIndex(i) << endl;
      ASSERT(false);
    }
  }
  
  // All children should now be notified their parent has gone AWOL.
  for (int i = 0; i < children.length(); i++)
  {
    Process *p = children.getByIndex(i);
    p->parent = NULL;
  }
  
  // Set the exit value.
  exitValue = val & 0x377;
  
  // We are now a zombie!! wooooo!!
  status = Zombie;
  
  // Loop indefinately until we are swapped out.
  // TODO: make this yield();
  startInterrupts();
  for (;;);
  
}

int Process::waitPid(int pid, int *stat_loc, int options)
{
  DEBUG_SPAM("waitPid(" << hex << pid <<  ", " << (u32int)stat_loc << ", " << options << ").");
  // Worth noting: If options->WNOHANG is unset, the spinning takes place
  // in the userland wrapper function, not here. This is so we occasionally
  // go back into userland, so any RPC requests fire.
  
  // Don't care about process groups at the moment.
  if (pid < -1)
  {
    pid *= -1;
  }

  // Is the pid specified an absolute pid reference?
  if (pid > 0)
  {
    // Does this process exist?
    if (!children.lookup(pid))
    {
      setErrno(ECHILD);
      return -1;
    }
    // Is it zombie?
    Process *p = children.lookup(pid);
    if (p->status == Zombie)
    {
      *stat_loc = p->exitValue;
      // We have waited for a zombie process. It should now be annhialated.
      processManager->processDying(pid);
      children.remove(pid);
      delete p;
      return pid;
    }
    else
    {
      return 0;
    }
  }
  else // Get any pid.
  {
    // Does this process have any children?
    if (!children.length())
    {
      setErrno(ECHILD);
      return -1;
    }
    // Are any zombies?
    for (int i = 0; i < children.length(); i++)
    {
      Process *p = children.getByIndex(i);
      if (p->status == Zombie)
      {
        *stat_loc = p->exitValue;
        // We have waited for a zombie process. It should now be annhialated.
        processManager->processDying(i);
        children.remove(p->getPid());
        int pid = p->getPid();
        delete p;
        END_KERNEL(pid);
      }
    }
    return 0;
  }
  setErrno(EINVAL);
  return -1;
}

void Process::queueRpcCall(class RpcCall *call)
{
  stopInterrupts();
  DEBUG_SPAM("Queueing RPC Call " << hex << (u32int)call << ".");
  
  if ((u32int)this < 0x100000)
  {
    kerr << "This is " << hex << (u32int)this << endl;
  }
  ASSERT((u32int)this > 0x100000);
  rpcQueue.insertLast(call);
  startInterrupts();
}

void Process::setCtty(File *t)
{
  ctty = t;
}

void Process::setCttyName(char *name)
{
  ctty = filesystem->traverse(name);
}

u32int Process::getCttyResource()
{
  if (ctty)
  {
    return ctty->resource->getId();
  }
  else
  {
    return 0;
  }
}

void Process::initialiseSignals(u32int resourceId)
{
  DEBUG_MSG("Initialising signals with resource " << dec << resourceId << ".");
  signalResource = (RemoteResource*)resourceManager.request(resourceId);
}
